package server

import (
	"fmt"
	"net"
	"noip_updator/common"
	"noip_updator/service"
	"time"

	"github.com/go-co-op/gocron"
	"github.com/spf13/viper"
)

type Server struct {
	service.IPer
	service.Updator
	service.Notify
}

func NewServer(iper service.IPer, updator service.Updator, notify service.Notify) *Server {
	return &Server{
		IPer:    iper,
		Updator: updator,
		Notify:  notify,
	}
}

func (sv *Server) MonPublicIPChange() {
	// 通知邮件
	notifyChan := make(chan service.NotifyContent, 1)
	defer close(notifyChan)
	if sv.Notify != nil {
		go sv.Notify.Listen(notifyChan)
	}
	ipMemo := &memo{
		v4: &savedIP{"0.0.0.0", false},
		v6: &savedIP{"::", false},
	}
	// 定时
	loc, err := time.LoadLocation("Local")
	if err != nil {
		loc, _ = time.LoadLocation("UTC")
	}
	scheduler := gocron.NewScheduler(loc)
	_, err = scheduler.Every(viper.GetInt("monitor.interval")).Minutes().Do(sv.monTask, notifyChan, ipMemo)
	if err != nil {
		common.Logger.Errorf("scheduler err:%v", err)
		return
	}
	scheduler.StartBlocking()
}

// v4、v6各保留一个即可
type memo struct {
	v4 *savedIP
	v6 *savedIP
}

type savedIP struct {
	ip      string
	updated bool
}

func (sv *Server) monTask(notifyChan chan<- service.NotifyContent, ipMemo *memo) {
	ips, err := sv.GetDynIP()
	if err != nil {
		common.Logger.Errorf("sv.GetDynIP err:%v", err)
		return
	}
	// 更新ddns
	success, ipChanged, resp := sv.updateDDNS(ips, ipMemo)
	if success && ipChanged {
		common.Logger.Info("ddns update succeed")
	} else if success && !ipChanged {
		common.Logger.Info("ddns not changed")
	} else {
		common.Logger.Warnf("ddns update failed:%s", resp)
	}
	// 发送通知
	if !viper.GetBool("notification.enable") {
		return
	}
	if success && !ipChanged { // 更新成功，但是IP没变的情况下压缩通知数量
		return
	}
	state := "INFO"
	if !success {
		state = "ERROR"
	}
	notifyChan <- service.NotifyContent{
		Subject: fmt.Sprintf("[%s]noip ddns update notification", state),
		Content: fmt.Sprintf(
			`<p>IPv4: %s, State: %v</p>
			<p>IPv6: %s, State: %v</p>
			<p>DDNS host updated: %v</p>
			<p>DDNS response: %s</p>`,
			ipMemo.v4.ip, ipMemo.v4.updated,
			ipMemo.v6.ip, ipMemo.v6.updated,
			success, resp,
		),
	}
}

func (sv *Server) updateDDNS(ips []net.IP, ipMemo *memo) (bool, bool, string) {
	ipMemo.v4.updated = false
	ipMemo.v6.updated = false
	var ipChanged bool
	for _, ip := range ips {
		if ipMemo.v4.updated && ipMemo.v6.updated {
			break
		}
		// v4地址
		if ip.To4() != nil {
			if ipMemo.v4.updated {
				continue
			}
			if ipMemo.v4.ip != ip.String() {
				ipChanged = true
			}
			ipMemo.v4.ip = ip.String()
			ipMemo.v4.updated = true
			continue
		}
		// v6地址
		if ipMemo.v6.updated {
			continue
		}
		if ipMemo.v6.ip != ip.String() {
			ipChanged = true
		}
		ipMemo.v6.ip = ip.String()
		ipMemo.v6.updated = true
		continue
	}
	// update ddns
	if !ipChanged {
		return true, ipChanged, "no change, update not needed"
	}
	var updateData []string
	if ipMemo.v4.updated {
		updateData = append(updateData, ipMemo.v4.ip)
	}
	if ipMemo.v6.updated {
		updateData = append(updateData, ipMemo.v6.ip)
	}
	if updateData == nil {
		return false, ipChanged, "no valid v4/v6 ip"
	}
	success, resp := sv.Update(updateData)
	return success, ipChanged, string(resp)
}
